package com.piece.core.framework.util.net;

import com.piece.core.framework.constant.ExceptionConstants;
import com.piece.core.framework.constant.FrameWorkConstants;
import com.piece.core.framework.enums.ContentType;
import com.piece.core.framework.enums.HeaderType;
import com.piece.core.framework.enums.MethodType;
import com.piece.core.framework.exception.ServerException;
import com.piece.core.framework.support.convert.Convert;
import com.piece.core.framework.support.convert.PatternPool;
import com.piece.core.framework.support.response.AjaxResponse;
import com.piece.core.framework.util.collection.ArrayUtil;
import com.piece.core.framework.util.collection.MapUtil;
import com.piece.core.framework.util.file.FileCharsetDetector;
import com.piece.core.framework.util.file.FileUtil;
import com.piece.core.framework.util.file.IoUtil;
import com.piece.core.framework.util.math.RandomUtil;
import com.piece.core.framework.util.object.ClassUtil;
import com.piece.core.framework.util.object.ObjectUtil;
import com.piece.core.framework.util.security.coder.Base64Coder;
import com.piece.core.framework.util.basic.I18nUtil;
import com.piece.core.framework.util.string.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.apache.commons.httpclient.HttpException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

@Slf4j
public class HttpUtil {

    public static final String HTTP_1_0 = "HTTP/1.0";
    public static final String HTTP_1_1 = "HTTP/1.1";
    public static final String BOUNDARY = "--------------------" + RandomUtil.randomString(16);
    public static final String BOUNDARY_END;
    public static final String CONTENT_TYPE_MULTIPART_PREFIX;
    public static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n";
    public static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n";
    public static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n";

    protected Charset charset;
    protected String httpVersion;

    private HttpURLConnection connection;
    private MethodType method;
    private int connectionTimeout;
    private int readTimeout;
    private String url;
    private Map<String, List<String>> headers = new HashMap();;
    private String cookie;
    private Map<String, Object> form;
    private boolean isMultipart;
    private byte[] bodyBytes;
    private int blockSize;
    private Proxy proxy;
    private boolean isDisableCache;
    private boolean isRest;
    private int redirectCount;
    private int maxRedirectCount;
    private InputStream in;
    private volatile boolean isAsync;

    static {
        BOUNDARY_END = StringUtil.format("--{}--\r\n", new Object[]{BOUNDARY});
        CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary=";
    }

    public HttpUtil(String url) {
        this.charset = StandardCharsets.UTF_8;
        this.httpVersion = HTTP_1_1;
        this.method = MethodType.GET;
        this.connectionTimeout = -1;
        this.readTimeout = -1;
        this.maxRedirectCount = 0;
        this.url = ofHttp(url);
        setDefaultHeaders();
        this.isMultipart = false;
    }

    public HttpUtil setDefaultHeaders() {
        this.header(HeaderType.CACHE_CONTROL.getValue(), "no-cache", true);
        this.header(HeaderType.ACCEPT.getValue(), "*/*", true);
        this.header(HeaderType.USER_AGENT.getValue(), "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", true);
        //this.header(HeaderType.ACCEPT_ENCODING.getValue(), "gzip, deflate", true);
        this.header(HeaderType.ACCEPT_LANGUAGE.getValue(), "zh-CN,zh;q=0.8", true);
        return this;
    }

    public String header(String name) {
        List<String> values = this.headerList(name);
        return ArrayUtil.isEmpty(values) ? null : values.get(0);
    }

    public HttpUtil header(String name, String value, boolean isOverride) {
        if (null != name && null != value) {
            List<String> values = this.headers.get(name.trim());
            if (!isOverride && !ArrayUtil.isEmpty(values)) {
                values.add(value.trim());
            } else {
                ArrayList<String> valueList = new ArrayList();
                valueList.add(value);
                this.headers.put(name.trim(), valueList);
            }
        }

        return this;
    }

    public HttpUtil header(Map<String, String> headers) {
        if (MapUtil.isEmpty(headers)) {
            return this;
        }

        Iterator iterator = headers.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = (Map.Entry) iterator.next();
            this.header(entry.getKey(), StringUtil.defaultString(entry.getValue(), ""), true);
        }

        return this;
    }

    public HttpUtil header(Map<String, List<String>> headers, boolean isOverride) {
        if (MapUtil.isEmpty(headers)) {
            return this;
        }

        Iterator iterator = headers.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, List<String>> entry = (Map.Entry) iterator.next();
            String name = entry.getKey();
            Iterator var = ((List) entry.getValue()).iterator();

            while (var.hasNext()) {
                String value = (String) var.next();
                this.header(name, StringUtil.defaultString(value, ""), isOverride);
            }
        }

        return this;
    }

    public HttpUtil removeHeader(String name) {
        if (name != null) {
            this.headers.remove(name.trim());
        }

        return this;
    }

    public List<String> headerList(String name) {
        if (StringUtil.isBlank(name)) {
            return null;
        } else {
            CaseInsensitiveMap<String, List<String>> headersIgnoreCase = new CaseInsensitiveMap(this.headers);
            return headersIgnoreCase.get(name.trim());
        }
    }

    public Map<String, List<String>> headers() {
        return Collections.unmodifiableMap(this.headers);
    }

    public HttpUtil httpVersion(String httpVersion) {
        this.httpVersion = httpVersion;
        return this;
    }

    public HttpUtil charset(String charset) {
        if (StringUtil.isNotBlank(charset)) {
            this.charset = (Charset.forName(charset));
        }
        return this;
    }

    public HttpUtil method(MethodType method) {
        this.method = method;
        return this;
    }

    public HttpUtil contentType(String contentType) {
        this.header(HeaderType.CONTENT_TYPE.getValue(), contentType, true);
        return this;
    }

    public HttpUtil userAgent(String userAgent) {
        this.header(HeaderType.USER_AGENT.getValue(), userAgent, true);
        return this;
    }

    public HttpUtil contentLength(int value) {
        this.header(HeaderType.CONTENT_LENGTH.getValue(), String.valueOf(value), true);
        return this;
    }

    public HttpUtil cookie(String cookie) {
        this.cookie = cookie;
        return this;
    }

    public HttpUtil cookie(HttpCookie... cookies) {
        return ArrayUtil.isEmpty(cookies) ? this.disableCookie() : this.cookie(ArrayUtil.join(cookies, ";"));
    }

    public HttpUtil cookie(Collection<HttpCookie> cookies) {
        return this.cookie(ArrayUtil.isEmpty(cookies) ? null : (HttpCookie[]) cookies.toArray(new HttpCookie[0]));
    }

    public HttpUtil enableDefaultCookie() {
        return this.cookie((String) null);
    }

    public HttpUtil disableCookie() {
        return this.cookie("");
    }

    public HttpUtil basicAuth(String username, String password) {
        return this.auth(buildBasicAuth(username, password, this.charset));
    }

    public HttpUtil basicProxyAuth(String username, String password) {
        return this.proxyAuth(buildBasicAuth(username, password, this.charset));
    }

    public HttpUtil bearerAuth(String token) {
        return this.auth(FrameWorkConstants.BEARER_TYPE + token);
    }

    public HttpUtil auth(String content) {
        this.header(HeaderType.AUTHORIZATION.getValue(), content, true);
        return this;
    }

    public HttpUtil proxyAuth(String content) {
        this.header(HeaderType.PROXY_AUTHORIZATION.getValue(), content, true);
        return this;
    }

    public static String buildBasicAuth(String username, String password, Charset charset) {
        String data = username.concat(":").concat(password);
        return FrameWorkConstants.BASIC_TYPE + Base64Coder.encode(new String(data.getBytes(charset)));
    }

    public HttpUtil keepAlive(boolean isKeepAlive) {
        this.header(HeaderType.CONNECTION.getValue(), isKeepAlive ? "Keep-Alive" : "Close", true);
        return this;
    }

    public boolean isKeepAlive() {
        String connection = this.header(HeaderType.CONNECTION.getValue());
        if (null == connection) {
            return !HTTP_1_0.equalsIgnoreCase(this.httpVersion);
        } else {
            return !"close".equalsIgnoreCase(connection);
        }
    }

    public HttpUtil timeout(int milliseconds) {
        this.connectionTimeout(milliseconds);
        this.readTimeout(milliseconds);
        return this;
    }

    public HttpUtil connectionTimeout(int milliseconds) {
        this.connectionTimeout = milliseconds;
        return this;
    }

    public HttpUtil readTimeout(int milliseconds) {
        this.readTimeout = milliseconds;
        return this;
    }

    public HttpUtil disableCache() {
        this.isDisableCache = true;
        return this;
    }

    public HttpUtil followRedirects(boolean isFollowRedirects) {
        return this.maxRedirectCount(isFollowRedirects ? 2 : 0);
    }

    public HttpUtil maxRedirectCount(int maxRedirectCount) {
        this.maxRedirectCount = Math.max(maxRedirectCount, 0);
        return this;
    }

    public HttpUtil proxy(String host, int port) {
        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
        return this.proxy(proxy);
    }

    public HttpUtil proxy(Proxy proxy) {
        this.proxy = proxy;
        return this;
    }

    public HttpUtil rest(boolean isRest) {
        this.isRest = isRest;
        return this;
    }

    public HttpUtil setChunkedStreamingMode(int blockSize) {
        this.blockSize = blockSize;
        return this;
    }

    public HttpUtil data(String data) {
        return this.data(data, (String) null);
    }

    public HttpUtil data(String data, String contentType) {
        byte[] bytes = data.getBytes(this.charset);
        this.body(bytes);
        this.form = null;

        if (null != contentType) {
            this.contentType(contentType);
        } else {
            contentType = ContentType.get(data).getValue();
            if (null != contentType && ContentType.isDefault(this.header(HeaderType.CONTENT_TYPE.getValue()))) {
                if (null != this.charset) {
                    contentType = ContentType.build(contentType, this.charset);
                }
                this.contentType(contentType);
            }
        }

        if (StringUtil.containsAny(contentType.toLowerCase(), new CharSequence[]{"json", "xml"})) {
            this.isRest = true;
            this.contentLength(bytes.length);
        }

        return this;
    }

    public HttpUtil body(byte[] bodyBytes) {
        if (null != bodyBytes) {
            this.bodyBytes = bodyBytes;
        }

        return this;
    }

    public HttpUtil form(String name, Object value) {
        if (!StringUtil.isBlank(name) && !ObjectUtil.isNull(value)) {
            this.bodyBytes = null;
            if (value instanceof File) {
                return this.form(name, (File) value);
            } else {
                String strValue;
                if (value instanceof Iterable) {
                    strValue = ArrayUtil.join((Iterable) value, ",");
                } else if (ArrayUtil.isArray(value)) {
                    if (File.class == ClassUtil.getComponentType(value)) {
                        return this.form(name, (File[]) value);
                    }

                    strValue = ArrayUtil.join((Object[]) value, ",");
                } else {
                    strValue = Convert.toStr(value, (String) null);
                }

                return this.putToForm(name, strValue);
            }
        } else {
            return this;
        }
    }

    public HttpUtil form(Object... parameters) {
        for (int i = 0; i < parameters.length; i += 2) {
            this.form(parameters[i].toString(), parameters[i + 1]);
        }
        return this;
    }

    public HttpUtil form(Map<String, Object> formMap) {
        if (MapUtil.isNotEmpty(formMap)) {
            formMap.forEach(this::form);
        }
        return this;
    }

    public HttpUtil formStr(Map<String, String> formMapStr) {
        if (MapUtil.isNotEmpty(formMapStr)) {
            formMapStr.forEach(this::form);
        }
        return this;
    }

    public HttpUtil form(String name, File file) {
        if (null != file) {
            if (!this.isKeepAlive()) {
                this.keepAlive(true);
            }

            this.isMultipart = true;
            return this.putToForm(name, file);
        }

        return this;
    }

    public HttpUtil form(String name, File... files) {
        if (ArrayUtil.isEmpty(files)) {
            return this;
        } else if (1 == files.length) {
            File file = files[0];
            return this.form(name, file);
        } else {
            HttpUtil request = this;
            if (!this.isKeepAlive()) {
                this.keepAlive(true);
            }
            this.isMultipart = true;
            Arrays.stream(files).map(file -> request.addToForm(name, file));
            return request;
        }
    }

    private HttpUtil putToForm(String name, Object value) {
        if (null != name && null != value) {
            if (null == this.form) {
                this.form = new LinkedHashMap();
            }

            this.form.put(name, value);
        }
        return this;
    }

    private HttpUtil addToForm(String name, Object value) {
        if (null != name && null != value) {
            if (null == this.form) {
                this.form = new LinkedHashMap();
            }

            Object object = this.form.get(name);
            if (object == null) {
                this.form.put(name, Stream.of(value).collect(Collectors.toList()));
            } else {
                ((List) object).add(value);
            }
            return this;
        }

        return this;
    }

    private void setTimeout() {
        if (this.connectionTimeout > 0) {
            this.connection.setConnectTimeout(this.connectionTimeout);
        }
        if (this.readTimeout > 0) {
            this.connection.setReadTimeout(this.readTimeout);
        }
    }

    private void setMethod() throws Exception {
        if (MethodType.POST.equals(method) || MethodType.PUT.equals(method) || MethodType.PATCH.equals(method) || MethodType.DELETE.equals(method)) {
            this.connection.setUseCaches(false);
        }

        this.connection.setRequestMethod(method.getName());
    }

    private void setHttpsInfo() throws Exception {
        if (isHttps(this.url)) {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
            SSLSocketFactory ssf = sslContext.getSocketFactory();

            HttpsURLConnection httpsConn = (HttpsURLConnection) this.connection;
            httpsConn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            httpsConn.setSSLSocketFactory(ssf);
        }

        String host = this.connection.getURL().getHost();
        this.header(HeaderType.HOST.getValue(), host, true);
    }

    private void setInstanceFollowRedirects() {
        this.connection.setInstanceFollowRedirects(this.maxRedirectCount > 0);
    }

    private void setChunkedStreamingMode() {
        if (this.blockSize > 0) {
            this.connection.setChunkedStreamingMode(this.blockSize);
        }
    }

    private void setHeaders(boolean isOverride) {
        if (MapUtil.isNotEmpty(this.headers)) {
            Iterator iterator = this.headers.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, List<String>> entry = (Map.Entry) iterator.next();
                String name = entry.getKey();
                Iterator var6 = ((List) entry.getValue()).iterator();

                while (var6.hasNext()) {
                    String value = (String) var6.next();
                    if (isOverride) {
                        this.connection.setRequestProperty(name, value);
                    } else {
                        this.connection.addRequestProperty(name, value);
                    }
                }
            }
        }
    }

    private void setMultipart() {
        this.connection.setRequestProperty(HeaderType.CONTENT_TYPE.getValue(), ContentType.MULTIPART.toString());
    }

    /**
     * 发起请求
     */
    public HttpUtil execute() {
        return execute(false);
    }

    /**
     * 发起请求
     */
    public HttpUtil executeAsync() {
        return execute(true);
    }

    /**
     * 发起请求
     */
    public HttpUtil execute(boolean async) {
        try {
            this.isAsync = async;
            this.urlWithParamIfGet();
            this.initConnection();
            this.send();
            this.sendRedirectIfPossible();
        } catch (Exception e) {
            log.error("Http发送{}请求异常：{}", this.method.name(), e.getMessage());
            this.close();
        }

        return this;
    }

    public String urlWithParamIfGet() throws Exception {
        replacePlusMark();
        // 会出现误转码
        // encodeUrlParam();
        if (MethodType.GET.equals(this.method) && !this.isRest) {
            this.url = this.url + getFormUrlEncoded();
        }

        return this.url;
    }

    public void initConnection() throws Exception {
        if (null != this.connection) {
            disconnect();
        }

        URL _url = new URL(url);
        if (null == this.proxy) {
            this.connection = (HttpURLConnection) _url.openConnection();
        } else {
            this.connection = (HttpURLConnection) _url.openConnection(this.proxy);
        }

        this.setTimeout();
        this.setMethod();
        this.setHttpsInfo();
        this.setInstanceFollowRedirects();
        this.setChunkedStreamingMode();

        if (null != this.cookie) {
            this.header(HeaderType.COOKIE.getValue(), this.cookie, true);
        } else {
            CookieManager cookieManager = new CookieManager(new ThreadLocalCookieStore(), CookiePolicy.ACCEPT_ALL);
            Map<String, List<String>> cookieHeader = cookieManager.get(new URI(this.url), new HashMap<>(0));
            this.header(cookieHeader, false);
        }
        this.setHeaders(true);

        if (this.isDisableCache) {
            this.connection.setUseCaches(false);
        }

        this.connection.setDoOutput(true);
        this.connection.setDoInput(true);
    }

    private void send() {
        try {
            if (!MethodType.POST.equals(this.method) && !MethodType.PUT.equals(this.method) && !MethodType.DELETE.equals(this.method) && !this.isRest) {
                this.connection.connect();
            } else if (this.isMultipart) {
                this.sendMultipart();
            } else {
                this.sendFormUrlEncoded();
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void sendFormUrlEncoded() throws Exception {
        if (StringUtil.isBlank(this.header(HeaderType.CONTENT_TYPE.getValue()))) {
            this.connection.setRequestProperty(HeaderType.CONTENT_TYPE.getValue(), ContentType.FORM_URLENCODED.toString(this.charset));
        }

        byte[] content;
        if (ArrayUtil.isNotEmpty(this.bodyBytes)) {
            content = this.bodyBytes;
        } else {
            content = this.getFormUrlEncoded().getBytes(this.charset);
        }

        IoUtil.write(this.connection.getOutputStream(), content, true, true);
    }

    /**
     * 转码链接地址中的+号
     * <p>前端通过encodeURI编码不会转换+号，需要将转换url参数部分的空格为加号</p>
     */
    public void replacePlusMark() {
        if (this.url.contains("?")) {
            String nonParamStr = this.url.substring(0, this.url.indexOf("?") + 1);
            String paramStr = this.url.substring(nonParamStr.length());
            this.url = nonParamStr + paramStr.replace(" ", "+");
        }
    }

    /**
     * 转码地址中最后部分的中文名称
     */
    private void encodeUrlParam() {
        String newUrl = "";
        String nonParamStr = "";
        String paramStr = "";
        if (this.url.contains("?")) {
            nonParamStr = this.url.substring(0, this.url.indexOf("?") + 1);
            paramStr = this.url.substring(nonParamStr.length());
        } else {
            nonParamStr = this.url;
        }

        newUrl +=nonParamStr;
        if (this.url.contains("?")) {
            String[] params = paramStr.split("&");
            paramStr = "";
            for (String param : params) {
                paramStr += "&";
                paramStr += param.split("=")[0] + "=";
                paramStr += URLEncoder.QUERY.encode(param.split("=")[1], this.charset);
            }

            paramStr = paramStr.substring(1);
            newUrl += paramStr;
        }
        this.url = newUrl;
    }

    private String getFormUrlEncoded() {
        if (MapUtil.isEmpty(this.form)) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        boolean isFirst = true;
        Iterator var4 = this.form.entrySet().iterator();

        while (var4.hasNext()) {
            Map.Entry<String, Object> entry = (Map.Entry) var4.next();
            if (isFirst) {
                isFirst = false;
            } else {
                sb.append("&");
            }

            String key = entry.getKey();
            if (StringUtil.isNotEmpty(key)) {
                sb.append(Convert.toStr(URLEncoder.PATH_SEGMENT.encode(key, this.charset)));
                String value = Convert.toStr(entry.getValue(), null);
                if (null != value) {
                    sb.append("=").append(Convert.toStr(URLEncoder.PATH_SEGMENT.encode(value, this.charset)));
                }
            }
        }

        return sb.toString();
    }

    public static String toParams(Map<String, ?> paramMap, Charset charset) {
        return URLQuery.of(paramMap).build(charset);
    }

    public static String encodeParams(String urlWithParams, Charset charset) {
        if (StringUtil.isEmpty(urlWithParams)) {
            return "";
        }

        String urlPart = null;
        int pathEndPos = urlWithParams.indexOf(63);
        String paramPart;
        if (pathEndPos > -1) {
            urlPart = StringUtil.subBefore(urlWithParams, pathEndPos);
            paramPart = StringUtil.subAfter(urlWithParams, pathEndPos + 1, 0);
            if (StringUtil.isBlank(paramPart)) {
                return urlPart;
            }
        } else {
            if (!StringUtil.contains(urlWithParams, '=')) {
                return urlWithParams;
            }

            paramPart = urlWithParams;
        }

        paramPart = normalizeParams(paramPart, charset);
        return StringUtil.isBlank(urlPart) ? paramPart : urlPart + "?" + paramPart;
    }

    public static String normalizeParams(String paramPart, Charset charset) {
        if (StringUtil.isEmpty(paramPart)) {
            return paramPart;
        } else {
            StringBuilder builder = new StringBuilder(paramPart.length() + 16);
            int len = paramPart.length();
            String name = null;
            int pos = 0;

            int i;
            for(i = 0; i < len; ++i) {
                char c = paramPart.charAt(i);
                if (c == '=') {
                    if (null == name) {
                        name = pos == i ? "" : paramPart.substring(pos, i);
                        pos = i + 1;
                    }
                } else if (c == '&') {
                    if (pos != i) {
                        if (null == name) {
                            name = paramPart.substring(pos, i);
                            builder.append(Convert.toStr(URLEncoder.PATH_SEGMENT.encode(name, charset)));
                            builder.append("=");
                        } else {
                            builder.append(Convert.toStr(URLEncoder.PATH_SEGMENT.encode(name, charset)));
                            builder.append("=");
                            builder.append(Convert.toStr(URLEncoder.PATH_SEGMENT.encode(paramPart.substring(pos, i), charset)));
                            builder.append('&');
                        }
                        name = null;
                    }
                    pos = i + 1;
                }
            }

            if (null != name) {
                builder.append(URLUtil.encodeQuery(name, charset)).append('=');
            }

            if (pos != i) {
                if (null == name && pos > 0) {
                    builder.append('=');
                }

                builder.append(URLUtil.encodeQuery(paramPart.substring(pos, i), charset));
            }

            int lastIndex = builder.length() - 1;
            if ('&' == builder.charAt(lastIndex)) {
                builder.deleteCharAt(lastIndex);
            }

            return builder.toString();
        }
    }

    public static Map<String, Object> decodeParamMap(String paramsStr, Charset charset) {
        String query = URLQuery.of(paramsStr, charset).build(charset);
        return StringUtil.isEmpty(query) ? MapUtil.empty() : Convert.toMap(query);
    }

    public static Map<String, List<String>> decodeParams(String paramsStr, String charset) {
        return decodeParams(paramsStr, Charset.forName(charset));
    }

    public static Map<String, List<String>> decodeParams(String paramsStr, Charset charset) {
        Map<CharSequence, CharSequence> queryMap = URLQuery.of(paramsStr, charset, true).getQuery();
        if (MapUtil.isEmpty(queryMap)) {
            return MapUtil.empty();
        } else {
            Map<String, List<String>> params = new LinkedHashMap();
            queryMap.forEach((key, value) -> {
                List<String> values = params.computeIfAbsent(StringUtil.string(key), (k) -> new ArrayList(1));
                values.add(StringUtil.string(value));
            });
            return params;
        }
    }

    private boolean isGzip() {
        String contentEncoding = this.header(HeaderType.CONTENT_ENCODING.getValue());
        return "gzip".equalsIgnoreCase(contentEncoding);
    }

    public boolean isDeflate() {
        String contentEncoding = this.header(HeaderType.CONTENT_ENCODING.getValue());
        return "deflate".equalsIgnoreCase(contentEncoding);
    }

    public boolean isChunked() {
        String transferEncoding = this.header(HeaderType.TRANSFER_ENCODING.getValue());
        return "Chunked".equalsIgnoreCase(transferEncoding);
    }

    public String contentEncoding() {
        return this.header(HeaderType.CONTENT_ENCODING.getValue());
    }

    public long getContentLength() {
        long contentLength = Convert.toLong(this.header(HeaderType.CONTENT_LENGTH.getValue()), -1L);
        if (contentLength > 0L && (this.isChunked() || StringUtil.isNotBlank(this.contentEncoding()))) {
            contentLength = -1L;
        }

        return contentLength;
    }

    private void sendRedirectIfPossible() throws Exception {
        if (this.maxRedirectCount < 1) {
            return;
        } else {
            if (this.connection.getInstanceFollowRedirects()) {
                int responseCode;
                try {
                    responseCode = this.connection.getResponseCode();
                } catch (IOException e) {
                    throw new HttpException(e.getMessage());
                }

                if (responseCode < 400 && isRedirected(responseCode)) {
                    String httpUrl = this.connection.getHeaderField(HeaderType.LOCATION.getValue());
                    this.url = httpUrl;
                    if (this.redirectCount < this.maxRedirectCount) {
                        ++this.redirectCount;
                        this.execute();
                    }
                }
            }
        }
    }

    private void sendMultipart() throws Exception {
        this.setMultipart();
        OutputStream out = this.connection.getOutputStream();
        writeForm(out);
    }

    private void writeForm(OutputStream out) throws Exception {
        if (MapUtil.isNotEmpty(this.form)) {
            Iterator iterator = this.form.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry) iterator.next();
                appendPart(entry.getKey(), entry.getValue(), out);
            }
        }

        write(out, BOUNDARY_END);
    }

    private void appendPart(String name, Object value, OutputStream out) throws Exception {
        if (!ArrayUtil.isArray(value) && !(value instanceof Collection)) {
            write(out, "--", BOUNDARY, "\r\n");
            if (value instanceof File) {
                File file = (File) value;
                InputStream is = new FileInputStream(file);
                String fileName = file.getName();
                write(out, StringUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, new Object[]{name, fileName}));
                write(out, StringUtil.format(CONTENT_TYPE_FILE_TEMPLATE, new Object[]{FileUtil.getContentType(fileName, ContentType.STREAM.getValue())}));
                IoUtil.copy(is, out);
                IoUtil.close(is);
            } else {
                write(out, StringUtil.format(CONTENT_DISPOSITION_TEMPLATE, new Object[]{name}));
                write(out, value);
            }

            write(out, "\r\n");
        } else {
            Object[] objects = null;
            if (ArrayUtil.isArray(value)) {
                objects = (Object[]) value;
            } else {
                objects = ((List) value).toArray();
            }
            Iterator iterator = Arrays.stream(objects).iterator();
            while (iterator.hasNext()) {
                appendPart(name, iterator.next(), out);
            }
        }
    }

    private void write(OutputStream out, Object... objs) throws Exception {
        IoUtil.write(out, false, objs);
    }

    /**
     * 获取返回执行结果
     */
    public AjaxResponse response() {
        try {
            forceSync();
            if (!this.connection.getHeaderFields().isEmpty()) {
                return AjaxResponse.successWithHeader(I18nUtil.message(ExceptionConstants.MESSAGE_SUCCESS), new String(this.bodyBytes, this.charset), this.connection.getHeaderFields());
            } else {
                return AjaxResponse.success(I18nUtil.message(ExceptionConstants.MESSAGE_SUCCESS), new String(this.bodyBytes, this.charset));
            }
        } catch (Exception e) {
            log.error("Http读取网页内容异常：{}", e.getMessage());
            return AjaxResponse.error(e.getMessage());
        }
    }

    private void read() throws Exception {
        int responseCode;

        try {
            responseCode = this.connection.getResponseCode();
            this.in = responseCode < 400 ? this.connection.getInputStream() : this.connection.getErrorStream();
        } catch (Exception e) {
            throw new Exception(e);
        }

        if (null == this.in) {
            this.in = new ByteArrayInputStream(StringUtil.format("Error request, response status: {}", new Object[]{responseCode}).getBytes());
        } else {
            if (isGzip() && !(this.in instanceof GZIPInputStream)) {
                try {
                    this.in = new GZIPInputStream(this.in);
                } catch (IOException e) {
                }
            } else if (isDeflate() && !(this.in instanceof InflaterInputStream)) {
                this.in = new InflaterInputStream(this.in, new Inflater(true));
            }
        }
    }

    private long readBody() throws IOException {
        long contentLength = this.getContentLength();
        long copyLength = -1L;

        try {
            read();
            if (contentLength == -1) {
                this.bodyBytes = IoUtil.readBytes(in);
            } else {
                ByteArrayOutputStream bos = new ByteArrayOutputStream((int) contentLength);
                copyLength = IoUtil.copy(in, bos, 8192, contentLength, false);
                this.bodyBytes = bos.toByteArray();
            }
        } catch (Exception e) {
            if (!(e.getCause() instanceof EOFException) && !StringUtil.containsIgnoreCase(e.getMessage(), "Premature EOF")) {
                throw new IOException(e.getMessage());
            }
        } finally {
            this.close();
        }

        return copyLength;
    }

    private void forceSync() {
        try {
            readBody();
        } catch (IOException e) {
            if (!(e.getCause() instanceof FileNotFoundException)) {
                throw new ServerException(e.getMessage());
            }
        } finally {
            if (this.isAsync) {
                this.isAsync = false;
            }
        }
    }

    private String readStream() {
        return readStream(false);
    }

    /**
     * 读取网络流
     * @param detector 是否转码
     */
    private String readStream(boolean detector) {
        StringBuffer buffer = new StringBuffer();
        InputStreamReader inputStreamReader = null;
        BufferedReader reader = null;

        try {
            read();
            // 进行文本流转码
            if (detector) {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] copy_buffer = new byte[1024];
                int len;
                while ((len = this.in.read(copy_buffer)) > -1) {
                    baos.write(copy_buffer, 0, len);
                }
                baos.flush();
                FileCharsetDetector.Observer observer = FileCharsetDetector.guessFileEncoding(new ByteArrayInputStream(baos.toByteArray()));
                if (null != observer) {
                    this.charset = Charset.forName(observer.getEncoding());
                }
                this.in = new ByteArrayInputStream(baos.toByteArray());
            }
            inputStreamReader = new InputStreamReader(this.in, this.charset.name());
            // 创建一个缓冲写入流
            reader = new BufferedReader(inputStreamReader);
            String line;
            while ((line = reader.readLine()) != null) {
                // 一行一行追加
                buffer.append(line + "\r\n");
            }

            return buffer.toString();
        } catch (Exception e) {
            log.error("Http抓取网页内容异常：{}", e.getMessage());
            throw new RuntimeException(e);
        } finally {
            IoUtil.close(inputStreamReader);
            this.close();
        }
    }

    /**
     * 获取返回文本执行结果
     */
    public String text() {
        try {
            return readStream(true);
        } catch (Exception e) {
            return e.getMessage();
        }
    }

    /**
     * 获取返回带header的执行结果
     */
    public AjaxResponse html() {
        try {
            String result = text();
            return AjaxResponse.success(I18nUtil.message(ExceptionConstants.MESSAGE_SUCCESS), result);
        } catch (Exception e) {
            return AjaxResponse.error(e.getMessage());
        }
    }

    public Document document() {
        Document document = null;
        try {
            AjaxResponse result = html();
            if (result.getCode() == 200 && result.getParam() != null) {
                document = Jsoup.parse((String) result.getParam());
            }
        } catch (Exception e) {
            log.error("Http抓取网页内容异常：{}", e.getMessage());
        }
        return document;
    }

    public InputStream download() {
        try {
            read();
            return this.in;
        } catch (Throwable e) {
            log.error("Http下载内容异常：{}", e.getMessage());
            return this.in;
        } finally {
            disconnect();
        }
    }

    public String download(OutputStream os) {
        try {
            read();
            long contentLength = this.getContentLength();
            IoUtil.copy(in, os, 8192, contentLength, false);
        } catch (Throwable e) {
            log.error("Http下载内容异常：{}", e.getMessage());
            throw new RuntimeException(e);
        } finally {
            disconnect();
            return getFileNameFromDisposition();
        }
    }

    public static String getString(InputStream in, Charset charset) throws IOException {
        byte[] contentBytes = IoUtil.readBytes(in);
        return new String(contentBytes, charset);
    }

    private String getFileNameFromDisposition() {
        String fileName = null;
        String disposition = this.header(HeaderType.CONTENT_DISPOSITION.getValue());
        if (StringUtil.isNotEmpty(disposition)) {
            Pattern pattern = PatternPool.get("filename=\"(.*?)\"", 32);
            Matcher matcher = pattern.matcher(disposition);
            fileName = matcher.find() ? matcher.group(1) : null;
            if (StringUtil.isEmpty(fileName)) {
                fileName = StringUtil.substring(disposition, "filename=", true);
            }
        }

        return fileName;
    }

    private boolean isIgnoreResponseBody() {
        return MethodType.HEAD == this.method || MethodType.CONNECT == this.method || MethodType.OPTIONS == this.method || MethodType.TRACE == this.method;
    }

    public void disconnect() {
        if (null != this.connection) {
            this.connection.disconnect();
        }
    }

    public void close() {
        IoUtil.close(this.in);
        this.in = null;
        disconnect();
    }

    public static HttpUtil get(String url) {
        return (new HttpUtil(url)).method(MethodType.GET);
    }

    public static HttpUtil post(String url) {
        return (new HttpUtil(url)).method(MethodType.POST);
    }

    public static HttpUtil delete(String url) {
        return (new HttpUtil(url)).method(MethodType.DELETE);
    }

    public static HttpUtil put(String url) {
        return (new HttpUtil(url)).method(MethodType.PUT);
    }

    public static boolean isRedirected(int responseCode) {
        return responseCode == 301 || responseCode == 302 || responseCode == 303 || responseCode == 307 || responseCode == 308;
    }

    public static boolean isHttp(String url) {
        return url.startsWith("http");
    }

    public static boolean isHttps(String url) {
        return url.startsWith("https");
    }

    public static String ofHttp(String url) {
        int sepIndex = url.indexOf("://");
        if (sepIndex < 0) {
            return "http://" + url.trim();
        }

        return url;
    }

    private static class TrustAnyTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    private static class ThreadLocalCookieStore implements CookieStore {
        private static final ThreadLocal<CookieStore> STORES = new ThreadLocal<CookieStore>() {
            protected synchronized CookieStore initialValue() {
                return (new CookieManager()).getCookieStore();
            }
        };

        public ThreadLocalCookieStore() {
        }

        public CookieStore getCookieStore() {
            return (CookieStore) STORES.get();
        }

        public ThreadLocalCookieStore removeCurrent() {
            STORES.remove();
            return this;
        }

        public void add(URI uri, HttpCookie cookie) {
            this.getCookieStore().add(uri, cookie);
        }

        public List<HttpCookie> get(URI uri) {
            return this.getCookieStore().get(uri);
        }

        public List<HttpCookie> getCookies() {
            return this.getCookieStore().getCookies();
        }

        public List<URI> getURIs() {
            return this.getCookieStore().getURIs();
        }

        public boolean remove(URI uri, HttpCookie cookie) {
            return this.getCookieStore().remove(uri, cookie);
        }

        public boolean removeAll() {
            return this.getCookieStore().removeAll();
        }
    }
}
